/*
 * Copyright (C) 2009-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Prototypes
 */
static void
propagate(east_t *ast);

static void
propagate_stat(east_t *ast);

static void
record(east_t *last, east_t *rast);

static void
coerce(eobject_t *pointer, east_t *last, east_t *rast);

static void
verify(east_t *ast);

/*
 * Initialization
 */
static ehash_t		*propagate_hash;

/*
 * Implementation
 */
void
init_const(void)
{
    eadd_root((eobject_t *)&propagate_hash);
    enew_hash((eobject_t *)&propagate_hash, 16);
}

void
finish_const(void)
{
    erem_root((eobject_t *)&propagate_hash);
}

void
econst(east_t *ast)
{
    eentry_t		*entry;
    eword_t		 offset;
    evector_t		*vector;

    /*   Information only valid for the current function. */
    if (propagate_hash->count) {
	for (offset = 0; offset < propagate_hash->size; offset++) {
	    for (entry = propagate_hash->entries[offset];
		 entry; entry = entry->next) {
		vector = entry->value;
		while (vector->offset) {
		    if (vector->v.ptr[--vector->offset])
			edel_object(vector->v.ptr + vector->offset);
		}
	    }
	}
	eclr_hash(propagate_hash);
    }

    propagate(ast);
}

void
efold(east_t *ast)
{
    east_t		*prev;
    east_t		*temp;

    switch (ast->token) {
	case tok_com:		case tok_plus:
	case tok_neg:		case tok_not:
	    eeval_unary(ast);
	    break;
	case tok_andand:	case tok_oror:
	    eeval_boolean(ast);
	    break;
	case tok_ne:		case tok_lt:
	case tok_le:		case tok_eq:
	case tok_ge:		case tok_gt:
	case tok_and:		case tok_or:
	case tok_xor:		case tok_add:
	case tok_sub:		case tok_mul:
	case tok_div:		case tok_trunc2:
	case tok_rem:		case tok_complex:
	    eeval_binary(ast);
	    break;
	case tok_mul2:		case tok_div2:
	case tok_shl:		case tok_shr:
	    eeval_shift(ast);
	    break;
	case tok_question:
	    if (ast->t.ast->token == tok_number) {
		if (efalse_p(ast->t.ast->l.value)) {
		    edel_object(&ast->l.value);
		    ast->l.ast = ast->r.ast;
		    ast->r.ast = null;
		}
		else
		    edel_object(&ast->r.value);
		edel_object(&ast->t.value);
		emove_left_ast_up(ast);
	    }
	    break;
	case tok_if:
	    temp = prev = ast->t.ast;
	    for (; temp->next; prev = temp, temp = temp->next)
		;
	    if (temp->token == tok_number) {
		if (efalse_p(temp->l.value)) {
		    edel_object(&ast->l.value);
		    ast->l.ast = ast->r.ast;
		    ast->r.ast = null;
		}
		else
		    edel_object(&ast->r.value);
		if (ast->l.ast && ast->l.ast->token == tok_stat)
		    emove_left_ast_up(ast);
		if (prev != temp) {
		    edel_object((eobject_t *)&prev->next);
		    prev->next = ast->l.ast;
		    ast->l.ast = ast->t.ast;
		    ast->t.ast = null;
		}
		else
		    edel_object(&ast->t.value);
		if (ast->l.ast &&
		    (ast->l.ast->token == tok_stat ||
		     ast->l.ast->token == tok_code))
		    emove_left_ast_up(ast);
		else
		    ast->token = tok_stat;
	    }
	    break;
	case tok_for:
	    if ((temp = ast->t.ast)) {
		for (prev = temp; temp->next; prev = temp, temp = temp->next)
		    ;
		if (temp->token == tok_number) {
		    if (efalse_p(temp->l.value)) {
			if (prev != temp) {
			    edel_object((eobject_t *)&prev->next);
			    if ((temp = ast->l.ast)) {
				for (; temp->next; temp = temp->next)
				    ;
				temp->next = ast->t.ast;
			    }
			    else
				ast->l.ast = ast->t.ast;
			    ast->t.ast = null;
			}
			else
			    edel_object(&ast->t.value);
			edel_object(&ast->r.value);
			edel_object(&ast->c.value);
			ast->token = tok_stat;
		    }
		}
	    }
	    break;
	case tok_do:
	    temp = prev = ast->t.ast;
	    for (; temp->next; prev = temp, temp = temp->next)
		;
	    if (temp->token == tok_number && efalse_p(temp->l.value)) {
		if (prev != temp) {
		    edel_object((eobject_t *)&prev->next);
		    for (temp = ast->c.ast; temp->next; temp = temp->next)
			;
		    temp->next = ast->t.ast;
		}
		else
		    edel_object(&ast->t.value);
		ast->l.ast = ast->c.ast;
		ast->t.ast = ast->c.ast = null;
		if (ast->l.ast->token == tok_stat ||
		    ast->l.ast->token == tok_code)
		    emove_left_ast_up(ast);
		else
		    ast->token = tok_stat;
	    }
	    break;
	case tok_while:
	    temp = prev = ast->t.ast;
	    for (; temp->next; prev = temp, temp = temp->next)
		;
	    if (temp->token == tok_number && efalse_p(temp->l.value)) {
		if (prev != temp) {
		    edel_object((eobject_t *)&prev->next);
		    ast->l.ast = ast->t.ast;
		    ast->t.ast = null;
		}
		else {
		    edel_object(&ast->t.value);
		    assert(ast->l.ast == null);
		}
		edel_object((eobject_t *)&ast->c.ast);
		ast->token = tok_stat;
	    }
	    break;
	default:
#if DEBUG
	    eparse_warn(ast,
			"efold: not handling %s", etoken_to_charp(ast->token));
#endif
	    break;
    }
}

static void
propagate(east_t *ast)
{
    east_t		*temp;

    switch (ast->token) {
	case tok_set:
	    propagate(ast->l.ast);
	    propagate(ast->r.ast);
	    if (ast->l.ast->token == tok_symbol &&
		ast->r.ast->token == tok_number)
		record(ast->l.ast, ast->r.ast);
	    break;
	case tok_andset:	case tok_orset:
	case tok_xorset:	case tok_mul2set:
	case tok_div2set:	case tok_shlset:
	case tok_shrset:	case tok_addset:
	case tok_subset:	case tok_mulset:
	case tok_divset:	case tok_trunc2set:
	case tok_remset:
	    propagate(ast->l.ast);
	    propagate(ast->r.ast);
	    break;
	case tok_vector:
	    propagate(ast->l.ast);
	    propagate_stat(ast->l.ast);
	    break;
	case tok_dot:
	    propagate(ast->l.ast);
	    break;
	case tok_andand:	case tok_oror:
	case tok_ne:		case tok_lt:
	case tok_le:		case tok_eq:
	case tok_ge:		case tok_gt:
	case tok_and:		case tok_or:
	case tok_xor:		case tok_mul2:
	case tok_div2:		case tok_shl:
	case tok_shr:		case tok_add:
	case tok_sub:		case tok_mul:
	case tok_div:		case tok_trunc2:
	case tok_rem:
	    propagate(ast->l.ast);
	    propagate(ast->r.ast);
	    efold(ast);
	    break;
	case tok_not:		case tok_com:
	case tok_plus:		case tok_neg:
	    propagate(ast->l.ast);
	    efold(ast);
	    break;
	case tok_inc:		case tok_dec:
	case tok_postinc:	case tok_postdec:
	    break;
	case tok_question:
	    propagate(ast->t.ast);
	    propagate(ast->l.ast);
	    propagate(ast->r.ast);
	    efold(ast);
	    break;
	case tok_if:
	    propagate_stat(ast->t.ast);
	    propagate_stat(ast->l.ast);
	    propagate_stat(ast->r.ast);
	    efold(ast);
	    break;
	case tok_return:
	    if (ast->l.ast)
		propagate(ast->l.ast);
	    break;
	case tok_switch:
	    propagate_stat(ast->t.ast);
	    propagate_stat(ast->c.ast);
	    break;
	case tok_for:
	    propagate_stat(ast->l.ast);
	    propagate_stat(ast->t.ast);
	    propagate_stat(ast->r.ast);
	    propagate_stat(ast->c.ast);
	    efold(ast);
	    break;
	case tok_do:		case tok_while:
	    propagate_stat(ast->c.ast);
	    propagate_stat(ast->t.ast);
	    efold(ast);
	    break;
	case tok_list:
	    propagate_stat(ast->l.ast);
	    temp = ast->l.ast;
	    /*   Check inner fields for the sake of clean debug output. */
	    if (temp->next == null && temp->r.value == null &&
		temp->t.value == null &&
		(temp->c.value == null || etype(temp->c.value) == t_value))
		emove_ast_up_full(ast, temp);
	    break;
	case tok_call:
	    propagate(ast->l.ast);
	    propagate_stat(ast->r.ast);
	    break;
	case tok_stat:		case tok_code:
	case tok_data:
	    propagate_stat(ast->l.ast);
	    break;
	case tok_decl:
	    propagate(ast->l.ast);
	    propagate_stat(ast->r.ast);
	    break;
	case tok_symbol:
	    verify(ast);
	    break;
	case tok_type:		case tok_number:	case tok_string:
	case tok_class:		case tok_label:		case tok_break:
	case tok_continue:	case tok_case:		case tok_default:
	case tok_goto:		case tok_function:
	    break;
	default:
#if DEBUG
	    eparse_warn(ast, "propagate: not handling %s",
			etoken_to_charp(ast->token));
#endif
	    break;
    }
}

static void
propagate_stat(east_t *ast)
{
    for (; ast; ast = ast->next)
	propagate(ast);
}

static void
record(east_t *last, east_t *rast)
{
    eentry_t		*entry;
    eword_t		 offset;
    esymbol_t		*symbol;
    evector_t		*vector;
    eobject_t		*pointer;

    assert(last->token == tok_symbol);
    symbol = last->l.value;
    offset = last->code;
    if (symbol->namespace == current_namespace &&
	symbol->offset >= current_function->local_offset) {
	if ((entry = eget_hash(propagate_hash, symbol)) == null) {
	    gc_ref(pointer);
	    enew_entry(pointer, symbol, null);
	    entry = *pointer;
	    eput_hash(propagate_hash, entry);
	    gc_dec();
	    enew_vector(&entry->value, t_void, (offset + 4) & ~3);
	}
	vector = entry->value;
	if (vector->length <= offset)
	    erenew_vector(vector, (offset + 4) & ~3);
	coerce(vector->v.ptr + offset, last, rast);
    }
}

static void
coerce(eobject_t *pointer, east_t *last, east_t *rast)
{
    etype_t		 type;
    erecord_t		*record;
    esymbol_t		*symbol;

    assert(last->token == tok_symbol);
    symbol = last->l.value;
    if (symbol->tag->type == tag_auto)
	type = t_undef;
    else if (symbol->tag->type == tag_basic) {
	record = (erecord_t *)symbol->tag->name;
	type = record->type;
    }
    else
	/*   Only numeric by value objects should reach here. */
	eparse_error(last, "incompatible types in assignment");

    ecoerce(pointer, type, rast->l.value);
}

static void
verify(east_t *ast)
{
    eentry_t		*entry;
    eobject_t		 value;
    evector_t		*vector;

    if ((entry = eget_hash(propagate_hash, ast->l.value)) &&
	(vector = entry->value) &&
	ast->code < vector->length &&
	(value = vector->v.ptr[ast->code])) {
	ecopy(&ast->l.value, value);
	if (ast->c.value) {
	    assert(etype(ast->c.value) == t_value);
	    ast->c.value = null;
	}
	ast->token = tok_number;
	ast->code = 0;
    }
}
